feat: read-only roominfo (owner/announcement/openim) on GroupSnapshot

This commit is contained in:
wuyouMaster
2026-09-25 00:06:48 +08:00
parent afb876250e
commit 99c6e923bc
3 changed files with 129 additions and 2 deletions
+17 -2
View File
@@ -180,6 +180,8 @@ export interface GroupSnapshot {
roomId: string
memberCount: number
groupName?: string
/** 只读 roominfo(contact.db / chatroom);字段可能随版本缺失。 */
roomInfo?: GroupRoomInfo
members: {
wxid: string
nickname: string
@@ -190,6 +192,18 @@ export interface GroupSnapshot {
}[]
}
/** 群 roominfo 只读字段(列名兼容多版本;缺失则为 undefined)。 */
export interface GroupRoomInfo {
roomId: string
owner?: string
announcement?: string
announcementEditor?: string
maxMemberCount?: number
chatName?: string
openImAccountType?: string
isOpenIm?: boolean
}
export interface GroupMembershipSnapshot {
roomId: string
memberIds: string[]
@@ -1007,7 +1021,7 @@ export function getGroupSnapshot(userMd5: string): GroupSnapshot | null {
avatar: member.m_nsHeadImgUrl || ''
}))
return { roomId, memberCount: members.length, members }
return { roomId, memberCount: members.length, members, roomInfo: wcdb4Client.getRoomInfo(roomId) || undefined }
}
export async function getGroupSnapshotAsync(userMd5: string): Promise<GroupSnapshot | null> {
@@ -1032,7 +1046,8 @@ export async function getGroupSnapshotAsync(userMd5: string): Promise<GroupSnaps
roomId,
groupName: session?.nickname || undefined,
memberCount: members.length,
members
members,
roomInfo: (await wcdb4Client.getRoomInfoAsync(roomId)) || undefined
}
}
+81
View File
@@ -5,6 +5,51 @@ import crypto from 'crypto'
import { createRequire } from 'module'
import { createConnection, Socket } from 'net'
import { getResourceRoots } from './resource-paths'
export type RoomInfoRow = {
roomId: string
owner?: string
announcement?: string
announcementEditor?: string
maxMemberCount?: number
chatName?: string
openImAccountType?: string
isOpenIm?: boolean
}
function pickString(row: Record<string, unknown>, keys: string[]): string | undefined {
for (const key of keys) {
const value = row[key]
if (typeof value === 'string' && value.trim()) return value.trim()
if (typeof value === 'number' && Number.isFinite(value)) return String(value)
}
return undefined
}
export function normalizeRoomInfoRow(chatroomId: string, row: Record<string, unknown>): RoomInfoRow {
const openImAccountType =
pickString(row, ['openim_acct_type', 'openimAcctType', 'openim_type']) || undefined
const maxRaw = pickString(row, ['max_member_count', 'maxMemberCount', 'member_max'])
return {
roomId: chatroomId,
owner: pickString(row, ['roomowner', 'room_owner', 'owner', 'owner_id']),
announcement: pickString(row, [
'textannouncement',
'announcement',
'announcement_',
'chatroomannouncement'
]),
announcementEditor: pickString(row, [
'announcement_editor',
'announcement_editor_',
'announcementeditor'
]),
maxMemberCount: maxRaw && Number.isFinite(Number(maxRaw)) ? Number(maxRaw) : undefined,
chatName: pickString(row, ['chatroomnick', 'chat_name', 'chatroom_name', 'displayname']),
openImAccountType,
isOpenIm: openImAccountType ? openImAccountType !== '0' && openImAccountType !== '' : undefined
}
}
import { wcdbDebugLog } from './wcdb-debug'
import type { ImageMessageCountProbe } from '../shared/image-text-index'
import { imageTextWindowToSeconds } from '../shared/image-text-index'
@@ -2678,6 +2723,42 @@ export class Wcdb4Client {
return this.accountRoot
}
/**
* 只读 roominfo(contact.db / chatroom 或兼容列名)。
*/
getRoomInfo(chatroomId: string): RoomInfoRow | null {
if (!this.wcdbExecQuery || !chatroomId.endsWith('@chatroom')) return null
const escaped = chatroomId.replace(/'/g, "''")
const sql = `SELECT * FROM chatroom WHERE chatroomname = '${escaped}' LIMIT 1`
try {
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbExecQuery!(handle, 'contact', '', sql, outJson)
)
return rows?.[0] ? normalizeRoomInfoRow(chatroomId, rows[0]) : null
} catch {
return null
}
}
async getRoomInfoAsync(chatroomId: string): Promise<RoomInfoRow | null> {
if (!this.wcdbExecQuery || !chatroomId.endsWith('@chatroom')) return null
const escaped = chatroomId.replace(/'/g, "''")
const sql = `SELECT * FROM chatroom WHERE chatroomname = '${escaped}' LIMIT 1`
try {
const rows = await this.callJsonAsync<Record<string, unknown>[]>(
this.wcdbExecQuery as unknown as KoffiAsyncFunction,
'contact',
'',
sql
)
return rows?.[0] ? normalizeRoomInfoRow(chatroomId, rows[0]) : null
} catch (error) {
console.warn('[WCDB4] getRoomInfoAsync failed:', error)
return null
}
}
/** 只读列收藏(favorite.db / fav_db_item),供导出与搜索。 */
async listFavoriteItems(limit = 200): Promise<Record<string, unknown>[]> {
if (!this.wcdbExecQuery) return []
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest'
import { normalizeRoomInfoRow } from '../../src/main/wcdb4-client'
describe('normalizeRoomInfoRow', () => {
it('maps versioned chatroom column aliases', () => {
expect(
normalizeRoomInfoRow('123@chatroom', {
chatroomname: '123@chatroom',
roomowner: 'wxid_owner',
textannouncement: '群公告',
announcement_editor_: 'wxid_editor',
max_member_count: 500,
chatroomnick: '测试群',
openim_acct_type: '2'
})
).toEqual({
roomId: '123@chatroom',
owner: 'wxid_owner',
announcement: '群公告',
announcementEditor: 'wxid_editor',
maxMemberCount: 500,
chatName: '测试群',
openImAccountType: '2',
isOpenIm: true
})
expect(normalizeRoomInfoRow('9@chatroom', { room_owner: 'wxid_b' })).toMatchObject({
owner: 'wxid_b',
isOpenIm: undefined
})
})
})