mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-08 07:21:19 +08:00
feat: read-only roominfo (owner/announcement/openim) on GroupSnapshot
This commit is contained in:
@@ -180,6 +180,8 @@ export interface GroupSnapshot {
|
|||||||
roomId: string
|
roomId: string
|
||||||
memberCount: number
|
memberCount: number
|
||||||
groupName?: string
|
groupName?: string
|
||||||
|
/** 只读 roominfo(contact.db / chatroom);字段可能随版本缺失。 */
|
||||||
|
roomInfo?: GroupRoomInfo
|
||||||
members: {
|
members: {
|
||||||
wxid: string
|
wxid: string
|
||||||
nickname: 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 {
|
export interface GroupMembershipSnapshot {
|
||||||
roomId: string
|
roomId: string
|
||||||
memberIds: string[]
|
memberIds: string[]
|
||||||
@@ -1007,7 +1021,7 @@ export function getGroupSnapshot(userMd5: string): GroupSnapshot | null {
|
|||||||
avatar: member.m_nsHeadImgUrl || ''
|
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> {
|
export async function getGroupSnapshotAsync(userMd5: string): Promise<GroupSnapshot | null> {
|
||||||
@@ -1032,7 +1046,8 @@ export async function getGroupSnapshotAsync(userMd5: string): Promise<GroupSnaps
|
|||||||
roomId,
|
roomId,
|
||||||
groupName: session?.nickname || undefined,
|
groupName: session?.nickname || undefined,
|
||||||
memberCount: members.length,
|
memberCount: members.length,
|
||||||
members
|
members,
|
||||||
|
roomInfo: (await wcdb4Client.getRoomInfoAsync(roomId)) || undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,51 @@ import crypto from 'crypto'
|
|||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { createConnection, Socket } from 'net'
|
import { createConnection, Socket } from 'net'
|
||||||
import { getResourceRoots } from './resource-paths'
|
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 { wcdbDebugLog } from './wcdb-debug'
|
||||||
import type { ImageMessageCountProbe } from '../shared/image-text-index'
|
import type { ImageMessageCountProbe } from '../shared/image-text-index'
|
||||||
import { imageTextWindowToSeconds } from '../shared/image-text-index'
|
import { imageTextWindowToSeconds } from '../shared/image-text-index'
|
||||||
@@ -2678,6 +2723,42 @@ export class Wcdb4Client {
|
|||||||
return this.accountRoot
|
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),供导出与搜索。 */
|
/** 只读列收藏(favorite.db / fav_db_item),供导出与搜索。 */
|
||||||
async listFavoriteItems(limit = 200): Promise<Record<string, unknown>[]> {
|
async listFavoriteItems(limit = 200): Promise<Record<string, unknown>[]> {
|
||||||
if (!this.wcdbExecQuery) return []
|
if (!this.wcdbExecQuery) return []
|
||||||
|
|||||||
@@ -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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user