mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-06 22:00:49 +08:00
feat: 新增群员统计与退群事件,并把本地索引集中到设置页
- 群员统计:档案中按群统计发言排行与未发言成员,可复制纯文本、按平台能力发送 - 退群事件永久保存,并以本地推断条目并入档案消息流 - 设置新增本地索引页,集中管理聊天记录索引与图片文字索引
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
GROUP_EXIT_EVENT_MESSAGE_TYPE,
|
||||
isGroupExitEventMessage,
|
||||
mergeGroupExitEvents,
|
||||
toGroupExitEventMessage
|
||||
} from '../../src/shared/group-exit-event-message'
|
||||
import type { GroupExitMonitorEvent } from '../../src/shared/group-exit-monitor'
|
||||
import type { Message } from '../../src/shared/types'
|
||||
|
||||
const at = (year: number, month: number, day: number, hour = 0, minute = 0): number =>
|
||||
new Date(year, month - 1, day, hour, minute).getTime()
|
||||
|
||||
const makeEvent = (overrides: Partial<GroupExitMonitorEvent> = {}): GroupExitMonitorEvent => ({
|
||||
id: 'evt-1',
|
||||
contactId: 'g'.repeat(32),
|
||||
roomId: 'room@chatroom',
|
||||
groupName: '研发群',
|
||||
memberWxid: 'wxid_a',
|
||||
memberName: '老张',
|
||||
previousCount: 10,
|
||||
currentCount: 9,
|
||||
delta: -1,
|
||||
message: '老张退出了研发群',
|
||||
detectedAt: at(2026, 9, 20, 14, 30),
|
||||
...overrides
|
||||
})
|
||||
|
||||
const makeMessage = (id: string, createTimeSec: number): Message => ({
|
||||
id,
|
||||
from: 'user',
|
||||
type: '文本',
|
||||
datetime: '2026-09-20 14:00',
|
||||
content: 'hello',
|
||||
isSender: false,
|
||||
createTime: createTimeSec
|
||||
})
|
||||
|
||||
describe('toGroupExitEventMessage', () => {
|
||||
it('把事件映射成系统分支的消息,并带专用 type 标记', () => {
|
||||
const message = toGroupExitEventMessage(makeEvent())
|
||||
|
||||
expect(message.id).toBe('group-exit:evt-1')
|
||||
expect(message.from).toBe('system')
|
||||
expect(message.type).toBe(GROUP_EXIT_EVENT_MESSAGE_TYPE)
|
||||
expect(message.content).toBe('老张退出了研发群')
|
||||
expect(message.isSender).toBe(false)
|
||||
expect(message.senderId).toBe('wxid_a')
|
||||
expect(message.sessionId).toBe('room@chatroom')
|
||||
expect(isGroupExitEventMessage(message)).toBe(true)
|
||||
})
|
||||
|
||||
it('⚠️ createTime 必须从毫秒换算成秒(全项目 Message.createTime 都是秒)', () => {
|
||||
const detectedAt = at(2026, 9, 20, 14, 30)
|
||||
const message = toGroupExitEventMessage(makeEvent({ detectedAt }))
|
||||
|
||||
// 直接塞毫秒会让事件被排到几万年之后,或者从时间轴上消失。
|
||||
expect(message.createTime).toBe(Math.floor(detectedAt / 1000))
|
||||
expect(message.createTime).toBeLessThan(1e11)
|
||||
})
|
||||
|
||||
it('真实微信系统消息不会被误判为退群事件', () => {
|
||||
const systemMessage: Message = {
|
||||
id: 'm1',
|
||||
from: 'system',
|
||||
type: '系统消息',
|
||||
datetime: '2026-09-20 14:00',
|
||||
content: '某某加入了群聊',
|
||||
isSender: false
|
||||
}
|
||||
|
||||
expect(isGroupExitEventMessage(systemMessage)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeGroupExitEvents', () => {
|
||||
it('按时间升序并入,事件落在正确位置', () => {
|
||||
const messages = [
|
||||
makeMessage('m1', Math.floor(at(2026, 9, 20, 10) / 1000)),
|
||||
makeMessage('m2', Math.floor(at(2026, 9, 20, 16) / 1000))
|
||||
]
|
||||
const events = [makeEvent({ detectedAt: at(2026, 9, 20, 14, 30) })]
|
||||
|
||||
const merged = mergeGroupExitEvents(messages, events)
|
||||
|
||||
expect(merged.map((message) => message.id)).toEqual(['m1', 'group-exit:evt-1', 'm2'])
|
||||
})
|
||||
|
||||
it('重复合并是幂等的(广播会反复刷新,不能越插越多)', () => {
|
||||
const messages = [makeMessage('m1', Math.floor(at(2026, 9, 20, 10) / 1000))]
|
||||
const events = [makeEvent()]
|
||||
|
||||
const once = mergeGroupExitEvents(messages, events)
|
||||
const twice = mergeGroupExitEvents(once, events)
|
||||
|
||||
expect(twice).toHaveLength(2)
|
||||
expect(twice.map((message) => message.id)).toEqual(once.map((message) => message.id))
|
||||
})
|
||||
|
||||
it('没有事件时原样返回同一个引用(避免无谓的重渲染)', () => {
|
||||
const messages = [makeMessage('m1', 1_700_000_000)]
|
||||
expect(mergeGroupExitEvents(messages, [])).toBe(messages)
|
||||
})
|
||||
|
||||
it('多个事件各自成组、保持时间顺序', () => {
|
||||
const messages = [makeMessage('m1', Math.floor(at(2026, 9, 20, 8) / 1000))]
|
||||
const events = [
|
||||
makeEvent({ id: 'e1', detectedAt: at(2026, 9, 20, 12) }),
|
||||
makeEvent({ id: 'e2', detectedAt: at(2026, 9, 20, 15) })
|
||||
]
|
||||
|
||||
const merged = mergeGroupExitEvents(messages, events)
|
||||
|
||||
expect(merged.map((message) => message.id)).toEqual([
|
||||
'm1',
|
||||
'group-exit:e1',
|
||||
'group-exit:e2'
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user