diff --git a/src/main/services/chat-service.ts b/src/main/services/chat-service.ts index 95fb5dc..13801b0 100644 --- a/src/main/services/chat-service.ts +++ b/src/main/services/chat-service.ts @@ -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 { @@ -1032,7 +1046,8 @@ export async function getGroupSnapshotAsync(userMd5: string): Promise, 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): 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[]>((handle, outJson) => + this.wcdbExecQuery!(handle, 'contact', '', sql, outJson) + ) + return rows?.[0] ? normalizeRoomInfoRow(chatroomId, rows[0]) : null + } catch { + return null + } + } + + async getRoomInfoAsync(chatroomId: string): Promise { + 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[]>( + 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[]> { if (!this.wcdbExecQuery) return [] diff --git a/tests/unit/room-info.test.ts b/tests/unit/room-info.test.ts new file mode 100644 index 0000000..6767bc7 --- /dev/null +++ b/tests/unit/room-info.test.ts @@ -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 + }) + }) +})