feat: 修改群聊日报生成与群成员信息展示

- 支持结构化 AI 群聊日报及移动端长图导出
- 支持日报时间范围和消息类型筛选
- 支持群成员及当前用户群昵称解析
- 移除消息加载更多并优化图片懒加载
- 增强头像处理与敏感密钥日志保护
This commit is contained in:
电摇小子
2026-06-29 16:43:01 +08:00
parent f6f8bc33f9
commit c00d581d9e
15 changed files with 1531 additions and 175 deletions
+77 -10
View File
@@ -65,6 +65,7 @@ export class Wcdb4Client {
private initialized = false
private displayNameCache = new Map<string, string>()
private avatarCache = new Map<string, string>()
private groupNicknameCache = new Map<string, Map<string, string>>()
private cachedSessions: Wcdb4Session[] | null = null
private wcdbInit: (() => number) | null = null
@@ -96,6 +97,9 @@ export class Wcdb4Client {
private wcdbGetGroupMembers:
| ((handle: number, chatroomId: string, outJson: WcdbVoidOut) => number)
| null = null
private wcdbGetGroupNicknames:
| ((handle: number, chatroomId: string, outJson: WcdbVoidOut) => number)
| null = null
private wcdbOpenMessageCursor:
| ((
handle: number,
@@ -234,6 +238,7 @@ export class Wcdb4Client {
this.cachedSessions = null
this.displayNameCache.clear()
this.avatarCache.clear()
this.groupNicknameCache.clear()
}
getSessions(): Wcdb4Session[] {
@@ -291,12 +296,7 @@ export class Wcdb4Client {
}
getMyAvatarUrl(): string | undefined {
const rawAccountName = path.basename(this.accountRoot)
const candidates = this.uniq([
this.wxid,
rawAccountName,
Wcdb4Client.cleanAccountDirName(rawAccountName)
])
const candidates = this.getMyUsernameCandidates()
this.hydrateAvatarUrls(candidates)
for (const candidate of candidates) {
@@ -307,6 +307,15 @@ export class Wcdb4Client {
return undefined
}
getMyGroupNickname(chatroomId: string): string | undefined {
const groupNicknames = this.getGroupNicknames(chatroomId)
for (const candidate of this.getMyUsernameCandidates()) {
const nickname = groupNicknames.get(candidate)
if (nickname) return nickname
}
return undefined
}
private getMessagesByCursor(
username: string,
startTime?: number,
@@ -413,11 +422,12 @@ export class Wcdb4Client {
if (!this.wcdbGetGroupMembers || !chatroomId) return []
try {
const groupNicknames = this.getGroupNicknames(chatroomId)
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbGetGroupMembers!(handle, chatroomId, outJson)
)
return (Array.isArray(rows) ? rows : []).map((row) => {
const members = (Array.isArray(rows) ? rows : []).map((row) => {
const username = this.pickString(row, [
'username',
'userName',
@@ -425,10 +435,15 @@ export class Wcdb4Client {
'member_username',
'm_nsUsrName'
])
const nickname = this.pickString(row, [
const memberNickname = this.pickString(row, [
'nickname',
'nickName',
'displayName',
'display_name',
'groupNickname',
'group_nickname',
'roomNickname',
'room_nickname',
'remark',
'm_nsNickName'
])
@@ -440,21 +455,60 @@ export class Wcdb4Client {
])
if (username) {
if (nickname) this.displayNameCache.set(username, nickname)
if (avatar) this.avatarCache.set(username, avatar)
}
return {
m_nsUsrName: username,
nickname: nickname || username,
nickname: groupNicknames.get(username) || memberNickname,
m_nsHeadImgUrl: avatar
}
})
const missingDisplayNames = members
.filter((member) => !member.nickname)
.map((member) => member.m_nsUsrName)
.filter(Boolean)
this.hydrateDisplayNames(missingDisplayNames)
return members.map((member) => ({
...member,
nickname:
member.nickname || this.displayNameCache.get(member.m_nsUsrName) || member.m_nsUsrName,
m_nsHeadImgUrl: member.m_nsHeadImgUrl || this.avatarCache.get(member.m_nsUsrName) || ''
}))
} catch {
return []
}
}
getGroupNicknames(chatroomId: string): Map<string, string> {
const cached = this.groupNicknameCache.get(chatroomId)
if (cached) return cached
const nicknames = new Map<string, string>()
if (!this.wcdbGetGroupNicknames || !chatroomId) return nicknames
try {
const rows = this.callJson<Record<string, string> | Record<string, unknown>[]>(
(handle, outJson) => this.wcdbGetGroupNicknames!(handle, chatroomId, outJson)
)
this.readStringMap(rows, [
'nickname',
'nickName',
'displayName',
'display_name',
'groupNickname',
'group_nickname',
'name'
]).forEach((nickname, username) => nicknames.set(username, nickname))
this.groupNicknameCache.set(chatroomId, nicknames)
} catch (error) {
console.warn(`[WCDB4] failed to get group nicknames for ${chatroomId}:`, error)
}
return nicknames
}
async getVoiceData(
sessionId: string,
createTime: number,
@@ -650,6 +704,14 @@ export class Wcdb4Client {
this.wcdbGetGroupMembers = null
}
try {
this.wcdbGetGroupNicknames = lib.func(
'int32 wcdb_get_group_nicknames(int64 handle, const char* chatroomId, _Out_ void** outJson)'
) as (handle: number, chatroomId: string, outJson: WcdbVoidOut) => number
} catch {
this.wcdbGetGroupNicknames = null
}
try {
this.wcdbOpenMessageCursor = lib.func(
'int32 wcdb_open_message_cursor(int64 handle, const char* sessionId, int32 batchSize, int32 ascending, int32 beginTimestamp, int32 endTimestamp, _Out_ int64* outCursor)'
@@ -1168,6 +1230,11 @@ export class Wcdb4Client {
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)))
}
private getMyUsernameCandidates(): string[] {
const rawAccountName = path.basename(this.accountRoot)
return this.uniq([this.wxid, rawAccountName, Wcdb4Client.cleanAccountDirName(rawAccountName)])
}
private normalizeTimestamp(input: number): number {
if (!input || input <= 0) return 0
const normalized = input > 1e12 ? Math.floor(input / 1000) : Math.floor(input)